﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace Babel
{
    internal class MethodSignatureEqualityComparer : IEqualityComparer<string>
    {
        #region Consts
        private static string NAME;
        private static string RET;
        private static string CLOSE;
        private static string ARG;
        private static string TYPE;
        private static string PTR;
        private static string TN;
        private static string G;
        private static string P;
        private static string EXCLAMATIVE;
        private static string INTERROGATIVE;
        private static string UNKNOWN;
        #endregion

        #region Fields
        // Match group of ()
        private static readonly Regex _reSign = new Regex(
                @"^(?:(?<name>[^(]*(?=\())[^()]*" +
                    @"(?:" +
                    @"(?:(?'open'\()[^()]*)+" +
                    @"(?:(?'close-open'\))[^()]*)+" +
                    @")*" +
                @"(?(open)(?!))(?:\s*:\s*))?(?=\w)?(?<ret>.*)$"
                , RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace);

        private static readonly Regex _reArgLs = new Regex(
                @"(?:" +
                @"(?<arg>(?<type>[^\[\]<,]+|(?<!-)[>]+)" +
                @"(?<gen>" +
                @"(?:" +
                @"(?:(?'open'<|\[)[^\[\]<]*|(?<!-)[>]*)+" +
                @"(?:(?'close-open'(?<!-)>|\])[^\[\]<,]*|(?<!-)[>]*)+" +
                @")*" +
                @"(?(open)(?!))" +
                @"))\s*,?\s*" +
                @")*"
                , RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace);

        private static readonly Regex _reArgs = new Regex(@"^(?<type>^[^<\[]*)(?:(?<p>[<])(?:(?<g>[^,]+),?\s*)+[>])?(?<a>\[(\s*(?<r>,)\s*)*?])?$"
                , RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace);

        private static readonly Regex _reArgsType = new Regex(@"^(?:(?<ptr>.*(?=->))->(?<tn>[^\s|$]*)\s*)?(?<name>.*)$"
                , RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace);

        private bool _compareFunctionNames;

        #endregion

        #region Properties
        public bool CompareFunctionNames
        {
            get { return _compareFunctionNames; }
            set { _compareFunctionNames = value; }
        }
        
        #endregion

        #region Constructors
        static MethodSignatureEqualityComparer()
        {
            InitStaticStrings();
        }

        private static void InitStaticStrings()
        {
            NAME = "name";
            RET = "ret";
            CLOSE = "close";
            ARG = "arg";
            TYPE = "type";
            PTR = "ptr";
            TN = "tn";
            G = "g";
            P = "p";
            EXCLAMATIVE = "!";
            INTERROGATIVE = "?";
            UNKNOWN = "Unknown";
        }

        public MethodSignatureEqualityComparer()
        {
        }
        
        #endregion

        #region IEqualityComparer<string> Members

        public bool Equals(string x, string y)
        {
            if (x.Equals(y, StringComparison.Ordinal))
                return true;

            Match m1 = _reSign.Match(x);
            if (!m1.Success) return false;

            Match m2 = _reSign.Match(y);
            if (!m2.Success) return false;

            // Check return value
            if (!ArgumentsAreEqual(m1.Groups[RET].Value, m2.Groups[RET].Value))
                return false;

            if (CompareFunctionNames)
            {
                string name1 = m1.Groups[NAME].Value;
                string name2 = m2.Groups[NAME].Value;

                if (!name1.Equals(name2, StringComparison.Ordinal))
                    return false;
            }

            string arglist1 = m1.Groups[CLOSE].Value;
            string arglist2 = m2.Groups[CLOSE].Value;

            // Check number of arguments
            if (arglist1.Length == 0 || arglist2.Length == 0)
                return ((arglist1.Length == 0) && (arglist2.Length == 0));

            if (arglist1.Equals(arglist2, StringComparison.Ordinal))
                return true;

            // Get argument list 
            string[] args1 = SplitArgumentList(arglist1);
            string[] args2 = SplitArgumentList(arglist2);

            if (args1.Length != args2.Length)
                return false;

            // Check arguments
            for (int i = 0; i < args1.Length; i++)
            {
                if (!ArgumentsAreEqual(args1[i].Trim(), args2[i].Trim()))
                {
                    return false;
                }
            }

            return true;
        }

        private string[] SplitArgumentList(string arglist)
        {
            List<string> args = new List<string>();
            Match m = _reArgLs.Match(arglist);
            if (m.Success)
            {
                foreach (Capture c in m.Groups[ARG].Captures)
                {
                    args.Add(c.Value);
                }                
            }
            return args.ToArray();
        }

        public int GetHashCode(string obj)
        {
            if (CompareFunctionNames)
                return obj.GetHashCode();

            Match m1 = _reSign.Match(obj);
            if (!m1.Success) 
                return obj.GetHashCode();

            return obj.Replace(m1.Groups[NAME].Value, String.Empty).GetHashCode();
        }

        #endregion

        #region Private Methods
        private bool ArgumentsAreEqual(string x, string y)
        {
            if (x.Equals(y, StringComparison.Ordinal))
                return true;

            Match m1 = _reArgs.Match(x);
            if (!m1.Success) return false;

            Match m2 = _reArgs.Match(y);
            if (!m2.Success) return false;

            // Check type are the same
            string t1 = m1.Groups[TYPE].Value;
            int c2 = m2.Groups[G].Captures.Count;
            if (IsGenericArg(t1))
                return (c2 == 0);

            string t2 = m2.Groups[TYPE].Value;
            int c1 = m1.Groups[G].Captures.Count;
            if (IsGenericArg(t2))
                return (c1 == 0);

            Match mt1 = _reArgsType.Match(t1);
            Match mt2 = _reArgsType.Match(t2);
            if (mt1.Success && mt2.Success)
            {
                string name1 = mt1.Groups[NAME].Value;
                string name2 = mt2.Groups[NAME].Value;
                if (name1 != name2)
                    return false;

                string ptr1 = mt1.Groups[PTR].Value;
                string ptr2 = mt2.Groups[PTR].Value;
                if (ptr1 != ptr2)
                    return false;

                string tn1 = mt1.Groups[TN].Value;
                string tn2 = mt2.Groups[TN].Value;
                if (tn1 != tn2)
                {
                    if (!(IsUnknownType(tn1) || IsUnknownType(tn2)))
                        return false;
                }
            }
            else
            {
                if (t1 != t2)
                    return false;
            }

            // Check number of generic parameters
            if (m1.Groups[G].Captures.Count != m2.Groups[G].Captures.Count)
                return false;

            // p contains [ for array and < for generic type
            if (m1.Groups[P].Value != m2.Groups[P].Value)
                return false;

            // Check generic parameters
            int args = m1.Groups[G].Captures.Count;

            for (int i = 0; i < args; i++)
            {
                string g1 = m1.Groups[G].Captures[i].Value;
                string g2 = m2.Groups[G].Captures[i].Value;

                if (IsGenericArg(g1) || IsGenericArg(g2))
                    continue;

                if (!ArgumentsAreEqual(g1, g2))
                {
                    return false;
                }
            }

            return true;
        }

        private bool IsGenericArg(string arg)
        {
            return arg.StartsWith(EXCLAMATIVE);
        }

        private bool IsUnknownType(string arg)
        {
            return arg.Contains(INTERROGATIVE) || arg.Contains(UNKNOWN);
        }
        
        #endregion
    }
}
